#include <deque>
#include <iostream>
#include <iomanip>
#include "simple/file.hpp"
#include "simple/support/enum.hpp"
#include "simple/support/misc.hpp"
#include "simple/support/algorithm.hpp"
#include "simple/support/iterator/match.hpp"

using namespace simple;
using namespace std::literals;

enum class Options
{
	Padding,
	Color,
	Source,
	Format,
	Separator,
	Wordwise,
	Invalid
};
using Option = support::mapped_enum<Options, Options::Invalid, 2>;
template <> Option::guts::map_type Option::guts::map
{{
	{ "-p"s, "--padding"s },
	{ "-c"s, "--color"s },
	{ "-i"s, "--source"s },
	{ "-f"s, "--format"s },
	{ "-s"s, "--separator"s },
	{ "-w"s, "--words"s },
}};

enum class Formats
{
	PlainText, OneLine, Hex,
	Invalid
};
using Format = support::mapped_enum<Formats, Formats::Invalid>;
template <> Format::guts::map_type Format::guts::map
{{
	{ "plain"s }, { "one-line"s }, { "hex"s },
}};

enum class Colors
{
	Black, Red,
	Green, Yellow,
	Blue, Magenta,
	Cyan, White,
	Invalid
};
using Color = support::mapped_enum<Colors, Colors::Invalid>;
template <> Color::guts::map_type Color::guts::map
{{
	{ "black"s }, { "red"s },
	{ "green"s }, { "yellow"s },
	{ "blue"s }, { "magenta"s },
	{ "cyan"s }, { "white"s }
}};

using buffer = std::vector<unsigned char>;
using const_range = support::range<buffer::const_iterator>;

template <typename Range>
void print(Range range, Format format, Color color = Colors::Invalid)
{
	assert(range.valid());
	auto cout_flags = std::cout.flags();
	std::cout << std::hex;
	if(Colors::Invalid != color)
		std::cout << "\33[4" << char('0' + (int)Colors(color)) << 'm';
	switch(format)
	{
		case Formats::Hex:
			if constexpr (std::is_same_v<Range, const_range>)
				for(auto&& c : range)
				{
					if(c <= 0xf)
						std::cout << '0';
					std::cout << +c;
				}
			else std::cerr << "ERROR: Hex format not supported for wordwise crop." << '\n';
		break;
		case Formats::OneLine:
			if constexpr (std::is_same_v<Range, const_range>)
				for(auto&& c : range)
				{
					auto cc = '\n' == c ? 'n' : '\r' == c ? 'r' : c;
					if(cc != c)
						std::cout << "\33[7m" << cc << "\33[27m";
					else
						std::cout << c;
				}
			else std::cerr << "ERROR: One line format not supported for wordwise crop." << '\n';
		break;
		default:
			for(auto&& c : range)
			{
				std::cout << c;
				if constexpr (not std::is_same_v<Range, const_range>)
					std::cout << ' ';
			}
	}
	if(Colors::Invalid != color)
		std::cout << "\33[0m";
	std::cout.flags(cout_flags);
}

auto split_words(const buffer& b)
{
	std::vector<std::string> words;
	simple::support::split(b, simple::support::match_iterator(simple::support::is_space), std::back_inserter(words));
	return words;
}

void crop(const std::string& filename, const std::string& range, Color color, int padding, Format format, bool wordwise)
{
	auto r = support::storn<int64_t>(range);
	const auto source = file::dump<buffer>(file::bropex(filename));

	auto do_print = [r, padding, format, color](const auto& source)
	{
		print(support::get_iterator_range<int64_t>(source, {r.lower()-padding , r.lower()}), format);
		print(support::get_iterator_range<int64_t>(source, r), format, color);
		print(support::get_iterator_range<int64_t>(source, {r.upper(), r.upper()+padding}), format);
	};

	if(wordwise)
		do_print(split_words(source));
	else
		do_print(source);
}

void process_arguments(std::deque<std::string> args)
{
	std::string filename;
	std::string separator;
	Color color;
	Format format;
	int padding = 0;
	bool wordwise = false;
	args.pop_front();
	while(!args.empty())
	{
		switch(Option(args.front()))
		{

			case Options::Padding:
				args.pop_front();
				padding = support::ston<int>(args.at(0));
				if(padding < 0)
					throw std::out_of_range("negative padding");
			break;

			case Options::Color:
				args.pop_front();
				color = Color(args.at(0));
			break;

			case Options::Source:
				args.pop_front();
				filename = args.at(0);
			break;

			case Options::Separator:
				args.pop_front();
				separator = args.at(0) + '\n';
			break;

			case Options::Format:
				args.pop_front();
				format = Format(args.at(0));
			break;

			case Options::Wordwise:
				args.pop_front();
				wordwise = support::ston<bool>(args.at(0));
			break;

			default:
				crop(filename, args.front(), color, padding, format, wordwise);
				std::cout << '\n' << separator;
			break;

		}
		args.pop_front();
	}
}

int main(int argc, char const* argv[]) try
{
	process_arguments({argv, argv + argc});
	return 0;
}
catch(...)
{
	if(errno) std::perror("Oh nooo!");
	throw;
}
